fix(core): read find() answers as QueryResult declares them in extractRecords - #8436
Merged
Merged
Conversation
…tRecords Remove the `records` arm from the shared `extractRecords` normaliser and pin the contract read per module. `QueryResult` (`@object-ui/types`) declares exactly one rows member, `data`. The ladder was `array -> records -> data -> value`, i.e. the undeclared spelling AHEAD of the contract's own member — the precedence inversion objectui#5945 was filed about and objectui#6726 repaired by hand at seven other seams. `records` is the below-the-adapter spelling that `ObjectStackAdapter.normalizeQueryResult` and `ApiDataSource .normalizeQueryResult` already fold into `data` below every consumer. Reach re-derived on this tree: ten call sites in nine packages. Seven direct (ObjectChart x2, ObjectDataTable, ObjectPivotTable, ObjectGantt, ObjectKanban, ObjectTimeline) and four through `applyNonGridRowCeiling` (`@object-ui/react`), itself a published export and a sink of its own (ObjectCalendar, ObjectGantt's rows, ObjectMap, ObjectTree). Producer measurement per consumer: no `find()` in those nine packages, nor in the apps and examples mounting them, emits a `records` envelope. Control — the same sweep finds `records` envelopes on other seams, including one live `find()` double at `plugin-list`'s ObjectGallery, a consumer with its own unwrap ladder, untouched here. The `value` arm stays: five `find()` doubles feed it today (plugin-kanban x3, plugin-calendar x2), and objectui#6840 explicitly refused to transfer its own zero to this seam. `QueryResult` is NOT widened. One refusal pin per module (`*.contractEnvelope-6839.*`), each keeping the live arms green alongside the deleted one, plus a direct pin on the helper for the precedence question a per-module render pin cannot ask. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
os-justin
marked this pull request as ready for review
September 7, 2026 23:22
os-justin
enabled auto-merge
September 7, 2026 23:22
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6839
Remove the
recordsarm from the sharedextractRecordsnormaliser(
packages/core/src/utils/extract-records.ts) and pin the contract read permodule. All work measured on
5dc25e3f5, the branch's only commit.QueryResult(@object-ui/types) declares exactly one rows member,data.The ladder was
array -> records -> data -> value, i.e. the undeclaredspelling ahead of the contract's own member — the precedence inversion
#5945 was filed about and #6726 repaired by hand at seven other seams. It is
now
array -> data -> value.1. What the card got wrong, re-derived on this base
The card was measured on
26896c689(#6726's base) and is stale in threeplaces. Every number below was re-derived on
0203a29e9.applyNonGridRowCeiling(@object-ui/react, #7210), which is itself a published export and an eleventh sink.ObjectChart.tsx:196(one site):303(group-by lookup labels) and:618(the chart rows). The card names one.ObjectTree:456,ObjectMap:714,ObjectCalendar:357extractRecordsany more — they reach it viaapplyNonGridRowCeilingat:561,:808,:490.ObjectGantt:680,:1132:752(indirect) and:1277(direct). Both still live.ObjectKanban:298/ObjectTimeline:213/ObjectDataTable:680:369/:315/:717.ObjectPivotTable:157:157, unchanged.ViewDataProvideralso calls it" — the card's central complication, repeated by triage, and the stated reason not to just delete the armViewDataProvider.ts:128declares its own privateextractRecords(records | data | items) and never imports the shared one. Verified on the card's own base:git show 26896c689:packages/core/src/data-scope/ViewDataProvider.tshas the same local declaration at the same line. The name collision was already recorded in this card's thread (#6917) the day after filing, and the triage comment five days later still asserted the dual seam.No renderer had been fixed since filing — all nine are still live consumers.
⇒ The card's architecture question ("A: split it, or B: hand it rows") is
moot: there is one seam. No escalation is owed under the dispatch's fence —
the two arms are not both legitimate on different driver answers, because
nothing at this seam produces
recordsat all.2. Producer measurement, per consumer
Cell, stated: every object-literal
records:key in every tracked.ts/.tsx/.js/.jsx/.mjsfile (143 hits / 103 files) — a complete population forliteral-form envelopes, with no body-scanner blind spot — plus three separate
sweeps for the shapes #6839's own thread warns a body-scanner cannot see:
mockResolvedValue-argument envelopes, brace-less arrowfindbodies, andcomputed/bracket-assigned keys.
Result: zero
recordsenvelopes reach any of the ten call sites. Perpackage, the
records:occurrences are doc comments, parameter names(
aggregateRecords(records: any[]),buildForest(records),.then((records: any) =>), bare-array locals passed asmockResolvedValue(records), and record-visibility batch route stubs(
fetch, notfind).Controls, so the zero is a reading and not a miss — the same sweeps are hot
elsewhere:
mockResolvedValuecell: subjectrecordsat aDataSource.finddouble = 0;control
data= 102 files; controlvalue= lit.findbodies: subject = 1 real hit,plugin-list/src/__tests__/ObjectGallery.expandFls-7429.test.tsx:117,find: vi.fn(async () => ({ records: [ROW] })). That producer feedsListView's own unwrap ladder (ListView.tsx:2060-2067), which does notcome through this helper. So the sweep demonstrably finds a producer of this
exact shape when one exists.
datalit.recordsat the seams below the adapters is unchanged and untouched:ObjectStackAdapter.normalizeQueryResult,ApiDataSource .normalizeQueryResult(its['data','items','results','records','value']loop),
ViewDataProvider's private reader,apiAccessDenied.test.ts'sclient-SDK doubles,
consoleUserPreferenceBudget.test.tsx'sds.client.data .find.3. The
valuearm stays#6840 / PR #6916 deleted
valuefromObjectView's ladder on a measured zeroat that seam and stated that its zero must not transfer here. Re-measured:
all five
find()doubles it named still exist and still emit{ value: [...] }into this helper —
plugin-kanbanx3,plugin-calendarx2 (the calendar pairnow arrives through
applyNonGridRowCeiling). Deleting it would break them.That is a separate card with its own measurement.
QueryResultis not widened to blessrecords: published-type change,maintainer's call, same floor #6726 and #6840 respected.
4. The pins — eleven files, 56 cases
One per module, named
*.contractEnvelope-6839.*, in #6726/#6916's form: thelive arms are asserted alongside the deleted one, through the same mount,
because live and dead is the whole distinction.
core/.../extract-records.contractEnvelope-6839recordsenvelope paint rows"; only a direct call can ask "when BOTH keys are present, which wins".react/.../nonGridRowCeiling.contractEnvelope-6839truncated: falseover a 2001-row result set — it would have silenced the row-ceiling footnote, not merely drawn nothing.ObjectChart(6 cases)ObjectGantt(6 cases)ObjectKanban,ObjectCalendarvaluearm, which is live only at these two packages.ObjectDataTable,ObjectPivotTableObjectTimeline,ObjectMap,ObjectTree5. Ablation — three legs, from the committed tree
Harness: absolute paths,
trap ... EXIT INT TERM, restore bygit checkout HEAD -- path(never the bare form, which restores from the index— i.e. from the mutation). Mutation proven on disk by printing the function body
and by hash; an empty hash is treated as failure, and a mutated hash equal to
the HEAD blob aborts the leg as a no-op. Restore proven by state: hash equal
to the HEAD blob AND
git diff HEADempty. No exit code is used as proofanywhere.
git rev-parse HEAD:packages/core/src/utils/extract-records.ts=d36ba54fe055c91ebe4afc91de8f10316ddc7bf4Module resolution note: the root
vitest.config.mtsaliases@object-ui/coreto
packages/core/src, so every leg resolves to source — no packageexportshop, nodist, and therefore no rebuild leg to get wrong.Baseline (unmutated):
VERDICT command-exit 0— 11 files / 56 passed.recordsback first (the bug)a9fd2799...VERDICT command-exit 1— 11 files, 17 failed / 39 passedreturn [](worse than the bug)3f319ecb...VERDICT command-exit 1— 11 files, 35 failed / 21 passedrecordskept, moved behinddata163d668e...VERDICT command-exit 1— 11 files, 15 failed / 41 passedRestore verified after every leg: blob back to
d36ba54f...,git diff HEADempty.
Leg A reddens by name, and only on the arm: every failing row is a
does NOT read recordscase, plus the three order cases —data OUTRANKS records: a producer emitting both is read as QueryResult,value outranks records too, anda refused records envelope reports NOT truncated. Every live-arm case stayedgreen.
Leg B answers the #8350 question directly: would an implementation strictly
worse than the bug pass these pins? No. A helper that returns
[]foreverything reddens in all eleven files (35 cases), because every module has
at least one non-regression case pushing the same rows through the same mount.
The cases that stay green under B are exactly the refusal cases — which is the
proof they are not the whole pin.
Leg C separates "reorder" from "delete", the distinction the card's own
title invites. A mere reorder makes
data OUTRANKS recordsgreen (the failinglist does not contain it) while all ten
does NOT read recordsrefusals stayred, plus
value outranks records too— because a reorder leaves theundeclared spelling reachable whenever
datais absent, which is precisely thetolerance AGENTS.md #0.1 is about.
6. Verification (all at
5dc25e3f5)Run through the container's shared heavy-verify lock; verdicts quoted from the
entry point, never a bare
$?.os-verify-lock: VERDICT command-exit 0— 306 files / 4473 tests passed(core, react, plugin-charts, plugin-timeline, plugin-kanban) and
VERDICT command-exit 0— 227 files / 1778 tests passed(plugin-dashboard, plugin-gantt, plugin-calendar, plugin-map, plugin-tree).
turbo run type-checkover the same ten (tsc --noEmit && tsc -p tsconfig.test.jsoneach):VERDICT command-exit 0— 26/26 tasks. The newpins are proven to be in the program they are quoted from:
tsc -p tsconfig.test.json --listFilesnamesextract-records.contractEnvelope-6839.test.ts(1 of 671) andObjectGantt.contractEnvelope-6839.test.tsx(1 of 1486), each with a litcontrol and, for core, a negative control that is correctly absent.
turbo run lintover the same ten:VERDICT command-exit 0— 11/11 tasks,0 errors.
check-changeset-presence("1 source file(s) of 1 releasedpackage(s) changed, and this change declares 1 changeset(s)"),
check-changeset-no-major,type-check coverage(42/42 packages compiletheir tests),
check-vi-mock-inherit,check-vi-mock-specifiers,check-control-bytes,check-phantom-dependencies,check-unused-dependencies,check-unreferenced-sources,check-package-self-import,check-lint-rule-coverage,check-element-data-source-declaration,check-comment-mask-corpus.check-governed-queue-guard --teston the 13 changed paths:NOT GOVERNED.Not measured locally, declared to CI, and reported as NOT MEASURED rather
than as a pass or a failure — three gates need a built tree this worktree does
not have, and each says so in its own words:
check-readme-exports("run
pnpm buildfirst"; its own census reports 21 unbuilt packages and itspopulation-collapse guard fires),
check-eager-closureandcheck-sdui-registration-pins(both "no console build ... this is a brokengauge, not a passing budget"). None of the three names any file in this diff
(verified with a lit control on the same grep), and this diff changes no README,
no export surface and no bundle.
7. Out of scope, not fixed here
plugin-list'sListViewhas the same family of arm, and there it isLIVE.
ListView.tsx:2060-2067readsdata -> records -> valueon its ownfind()answer, andObjectGallery.expandFls-7429.test.tsx:117is a livefind()double emitting{ records: [ROW] }that depends on it. Differentconsumer, its own ladder, its own producer — untouched here. It also means the
repo-wide phrasing "no producer emits
recordsat theDataSource.find()seam" is consumer-local, not seam-wide: it is true of this helper's ten
call sites and false of
ListView. Reported to the PM for filing —search_issuesis hard rate-limited for this session's account("API rate limit already exceeded for user ID 323634890"), and the standing
rule is not to file unsearched.
ViewDataProvider's private same-nameextractRecords(a third toleranceset,
records | data | items) is already filed as finding(fields,plugin-detail,plugin-list,app-shell): six more non-QueryResultcount/valuearms survive the seams #6840 repaired — and one of them readsvalueAHEAD ofdata#6917.🤖 Generated with Claude Code
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Generated by Claude Code